var NetPad = {
	connection: null,
	collaborator: null,
	
	NS_NETPAD: "http://muchine.net/ns/netpad",
	master: null,
	
	onDiscoInfo: function(iq) {
		NetPad.log("Received disco request. from: " + $(iq).attr('from'));
		
		NetPad.connection.sendIQ(
			$iq({to: $(iq).attr('from'), id: $(iq).attr('id'), type: "result"})
				.c('query', {xmlns: Strophe.NS.DISCO_INFO})
					.c('identity', {category: 'client', type: 'pc'}).up()
					.c('feature', {'var': NetPad.NS_NETPAD})
		);
		
		return true;
	},
	
	onCollaborate: function(presence) {
		// master receives collaboration request from another party
		var from = $(presence).attr('from');
		
		NetPad.log('directed presence received from ' + from);
		
		if (NetPad.collaborator) {
			// we already have a collaborator
			NetPad.connection.send(
				$pres({to: from, type: "error"})
					.c('error', {type: "wait"})
						.c('recipient-unavailable', {xmlns: Strophe.NS.STANZAS}).up()
						.c('already-collaborating', {xmlns: NetPad.NS_NETPAD})
			);
		}
		else {
			NetPad.collaborator = from;
			NetPad.startCollaboration();
		}
		
		return true;
	},
	
	onMessage: function(message) {
		var from = $(message).attr('from');
		
		if (NetPad.collaborator == from) {
			var collab = $(message).find('*[xmlns="' + NetPad.NS_NETPAD + '"]');
			
			if (collab.length > 0) {
				if (NetPad.master) {
					// handle state changes
					NetPad.processOp(collab);
				}
				else {
					var command = collab[0].tagName;
					if (command == 'start')	{
						$('#pad').val(collab.text());
						NetPad.startCollaboration();
					}
					else if (command == 'stop') {
						NetPad.stopCollaboration();
					}
					else {
						// handle state changes
						NetPad.processOp(collab);
					}
					
					NetPad.log("collab command: " + command);
				}
			}
			else {
				// add regular message to the chat
				var body = $(message).find('body').text();
				$('#chat').append(
					"<div class='message'>" +
					"&lt;<span class='nick'>" + Strophe.getBareJidFromJid(from) + "</span>&gt;" +
					"<span class='message'>" + body + "</span>" +
					"</div>"
				);
				NetPad.scrollChat();
			}
		}
		
		return true;
	},
	
	onUnavailable: function(presence) {
		var from = $(presence).attr('from');
		if (from == NetPad.collaborator) {
			NetPad.stopCollaboration();
		}
		
		return true;
	},
	
	startCollaboration: function() {
		$('#status').text('Collaborating with ' + NetPad.collaborator + '.').attr('class', 'collab');
		$('#input').removeAttr('disabled');
		
		// initialize optrans object
		var buffer = $('#pad').val();
		opTrans.init([NetPad.connection.jid, NetPad.collaborator], buffer, NetPad.updatePad);
		
		if (NetPad.master) {
			// set up and send initial collaboration state
			var message = 
				$msg({to: NetPad.collaborator, type: "chat"})
					.c('start', {xmlns: NetPad.NS_NETPAD});
			
			if (buffer) {
				message.t(buffer);
			}
			
			NetPad.connection.send(message);
			
			// enable stop button
			$('#stop').removeAttr('disabled');
		}
		else {
			$('#pad').removeAttr('disabled');
		}
	},
	
	stopCollaboration: function(notify) {
		$('#status').text('Not collaborating.').attr('class', 'no-collab');
		$('#stop').attr('disabled', 'disabled');
		$('#input').attr('disabled', 'disabled');
		
		if (notify) {
			NetPad.log("notify stopping collaboration to " + NetPad.collaborator);
			
			NetPad.connection.send(
				$msg({to: NetPad.collaborator, type: "chat"})
					.c('stop', {xmlns: NetPad.NS_NETPAD})
			);
		}
		
		NetPad.collaborator = null;
	},
	
	updatePad: function(buffer, remote) {
		var oldPos = $('#pad')[0].selectionStart;
		var oldBuffer = $('#pad').val();
		$('#pad').val(buffer);
		
		if (buffer.length > oldBuffer.length && !remote) {
			oldPos += 1;
		}
		
		$('#pad')[0].selectionStart = oldPos;
		$('#pad')[0].selectionEnd = oldPos;
	},
	
	onKeyPress: function(pad, ev) {
		if (NetPad.collaborator) {
			var idx = pad.selectionStart;
			
			NetPad.log("ev.which: " + ev.which);
			
			/*
			 * NOTE: on Chrome, delete and backspace keypress event is not fired.
			 * For this to be solved, we should use keyup event but the behavior becomes weird.
			 */
			if (ev.which == 8) {
				NetPad.log("backspace pressed.");
				
				pad.selectionStart = idx - 1;
				pad.selectionEnd = idx - 1;
				NetPad.sendOp('delete', idx - 1);
			}
			else if (ev.which == 127) {
				NetPad.log("delete pressed.");
				NetPad.sendOp('delete', idx);
			}
			else if ((ev.which >= 32 && ev.which < 127) || ev.which >= 256) {
				NetPad.sendOp('insert', idx, String.fromCharCode(ev.which));
			}
			
			ev.preventDefault();
		}
	},
	
	sendOp: function(op, pos, chr) {
		var req = opTrans.doLocal(op, pos, chr);
		var i;
		
		var opAttrs = {xmlns: NetPad.NS_NETPAD, name: op, pos: pos};
		if (chr) {
			opAttrs['char'] = chr;
		}
		
		var message = 
			$msg({to: NetPad.collaborator, type: "chat"})
				.c('op', opAttrs);
				
		message.c('state');
		for (i = 0; i < req[1].length; i++) {
			message.c('cell').t('' + req[1][i]).up();
		}
		
		message.up().c('priority');
		for(i = 0; i < req[3].length; i++) {
			message.c('cell').t('' + req[3][i]).up();
		}
		
		NetPad.connection.send(message);
	},
	
	processOp: function(op) {
		var name = op.attr('name');
		var pos = parseInt(op.attr('pos'), 10);
		var chr = op.attr('char');
		var pri = [];
		var state = [];
		
		NetPad.log("process op... " + name + ", " + pos + ", " + chr);
		
		op.find('state > cell').each(function() {
			state.push(parseInt($(this).text(), 10));
		});
		
		op.find('priority > cell').each(function() {
			pri.push(parseInt($(this).text(), 10));
		});
		
		opTrans.doRemote(NetPad.collaborator, state, name, pos, chr, pri);
	},
	
	sendMessage: function(chat, message) {
		$(chat).append(
			"<div class='message'>" +
			"&lt;<span class='nick self'>" + Strophe.getBareJidFromJid(NetPad.connection.jid) + "</span>&gt;" +
			"<span class='message'>" + message + "</span>" +
			"</div>"
		);
		
		NetPad.scrollChat();
		
		NetPad.connection.send(
			$msg({to: NetPad.collaborator, type: "chat"})
				.c('body').t(message)
		);
	},
	
	scrollChat: function() {
		var chat = $('#chat').get(0);
		chat.scrollTop = chat.scrollHeight;
	},
	
	log: function(message) {
		$('#log').prepend("<div>" + message + "</div>");
	}
};

$(document).bind('connect', function(ev, data) {
	var boshUrl = data.server;
	$('#status').text('Connecting to ' + boshUrl + '...');
	var conn = new Strophe.Connection(boshUrl);
	
	conn.connect(data.jid, data.password, function(status) {
		if (status == Strophe.Status.CONNECTED) {
			$(document).trigger('connected');
		}
		else if (status == Strophe.Status.DISCONNECTED) {
			$(document).trigger('disconnected');
		}
	});
	
	NetPad.connection = conn;
	NetPad.collaborator = data.collaborator || null;
});

$(document).bind('connected', function() {
	$('#disconnect').removeAttr('disabled');
	
	NetPad.connection.addHandler(NetPad.onMessage, null, "message");
	NetPad.connection.addHandler(NetPad.onUnavailable, null, "presence", "unavailable");
				
	// When communicating with Openfire, presence should be available in order to handle disco request and response.
	NetPad.connection.send($pres());
	
	if (NetPad.collaborator) {
		NetPad.master = false;
		
		$('#status').text('Checking feature support for ' + NetPad.collaborator + '.').attr('class', 'try-collab');
		
		// check for feature support
		NetPad.connection.sendIQ(
			$iq({to: NetPad.collaborator, type: "get"})
				.c('query', {xmlns: Strophe.NS.DISCO_INFO}),
			function(iq) {
				var f = $(iq).find('feature[var="' + NetPad.NS_NETPAD + '"]');
				if (f.length > 0) {
					$('#status').text('Establishing session with ' + NetPad.collaborator + '.').attr('class', 'try-collab');
					
					// request editing session
					NetPad.connection.send(
						$pres({to: NetPad.collaborator})
							.c('collaborate', {xmlns: NetPad.NS_NETPAD})
					);
				}
				else {
					$('#status').text('Collaboration not supported with ' + NetPad.collaborator + '.').attr('class', 'no-collab');
					NetPad.connection.disconnect();
				}
				
				// Test
				NetPad.log("from: " + $(iq).attr('from'));						
				$(iq).find('feature').each(function() {
					NetPad.log("feature: " + $(this).attr('var'));	
				});
			}
		);
	}
	else {
		NetPad.master = true;
		$('#status').text('Started as master mode. resource: ' + Strophe.getResourceFromJid(NetPad.connection.jid))
			.attr('class', 'no-collab');
		$('#pad').removeAttr('disabled');
		
		// handle incoming discovery requests
		NetPad.connection.addHandler(NetPad.onDiscoInfo, Strophe.NS.DISCO_INFO, "iq", "get");
		
		// NOTE: namespace should be specified?
		NetPad.connection.addHandler(NetPad.onCollaborate, NetPad.NS_NETPAD, "presence");		
	}
});

$(document).bind('disconnected', function() {
// 			$('#status').text('disconnected.').attr('class', 'no-collab');

	NetPad.connection = null;
	NetPad.collaborator = null;
	
	$('#login_dialog').dialog('open');
});